use super::{
    err, err_value, json, ok, throw_err, Base, Client, ECode, Lock, MapSv, RNull, Resp, TBase,
    VBase, VMap, VSend, Value, MSV,
};
use base::tbase::{update_side, update_stock};
use std::collections::HashMap;
use tracing::{instrument, warn};

pub struct Pick;

impl Base for Pick {}
impl TBase for Pick {}

impl Pick {
    pub async fn do_cmd(
        &self,
        para: &Value,
        cmd: &str,
        client: &Client,
        lock: Lock,
        tx: VSend,
    ) -> Resp {
        let data = para.ckobj()?;
        let line = data.ck_str("line")?;

        match cmd {
            "add" => {
                let tp = data.ck_str("tp")?;
                let sn = data.ck_str("sn")?;
                let box_sn = data.k_str("box");

                if tp != "outer" && tp != "inner" {
                    throw_err!(ECode::Para, "tp error");
                }

                let cmd = json!({ "line": line, "k": sn });
                let (md, old_box) = match client.cfgdb(&cmd, &format!("{tp}/get")).await {
                    Ok(body) => {
                        let body = body.ck_obj("data")?;

                        let old_box = match body.k_str_empty_trim("box") {
                            Some(v) => v,
                            None => "",
                        };

                        ("mditem", old_box.to_string())
                    }
                    Err(_) => ("add", "".to_string()),
                };

                let mut val = HashMap::from([
                    ("sn", json!(sn)),
                    ("time", json!(self.cur_time("%Y-%m-%d %H:%M:%S"))),
                ]);

                if let Some(boxsn) = box_sn {
                    val.insert("box", json!(boxsn));
                }

                let cmd = json!({
                    "line": line,
                    "v": val,
                });

                client.cfgdb(&cmd, &format!("{tp}/{md}")).await?;

                if let Some(boxsn) = box_sn {
                    if !old_box.is_empty() && old_box != boxsn {
                        let cmd = json!({ "line": line, "sn": sn });
                        let _ = client.cfgdb(&cmd, &format!("file/rm/box_{old_box}")).await;
                    }
                }

                let mut val = data.clone();
                val.insert("cmd".to_string(), json!("update"));

                let _ = tx.send(json!(val)).await;

                ok!()
            }
            "box" => self.do_box(line, data, client).await,
            "chunk" => self.do_chunk(line, data, client, lock).await,
            "count" => self.do_count(line, client).await,
            "free" => {
                let sn = data.ck_str("k")?;

                let cmd = json!({ "line": line });
                client.cfgdb(&cmd, &format!("file/rmfile/box_{sn}")).await?;

                if let Err(e) = self.free_box(line, "inner", sn, client).await {
                    warn!("free inner error: {:?}", e);
                }

                if let Err(e) = self.free_box(line, "outer", sn, client).await {
                    warn!("free outer error: {:?}", e);
                }

                ok!()
            }
            "list" => self.do_list(line, client).await,
            "load" => self.do_load(line, data, client).await,
            _ => throw_err!(ECode::NoServe, "no pick serve"),
        }
    }

    async fn free_box(&self, line: &str, tp: &str, box_sn: &str, client: &Client) -> RNull {
        let cmd = json!({ "line": line });
        let body = client.cfgdb(&cmd, &format!("{tp}/list")).await?;
        let body = body.ck_array("data")?;

        let mut ret = Vec::new();

        for d in body.iter() {
            let d = d.ckobj()?;

            let b = match d.k_str_empty_trim("box") {
                Some(v) => v,
                None => {
                    continue;
                }
            };

            if b != box_sn {
                continue;
            }

            ret.push(json!({
                "sn": d.ck_str("sn")?,
                "box": "",
            }));
        }

        if ret.is_empty() {
            return Ok(());
        }

        for d in ret.chunks(100) {
            let cmd = json!({
                "line": line,
                "v": d
            });

            let _ = client.cfgdb(&cmd, &format!("{tp}/md")).await;
        }

        Ok(())
    }

    async fn get_pick(&self, line: &str, tp: &str, client: &Client) -> Result<MSV, Value> {
        let cmd = json!({ "line": line });
        let mut ret = MSV::new();

        let body = client.cfgdb(&cmd, &format!("{tp}/list")).await?;
        let body = body.ck_array("data")?;

        for d in body.iter() {
            let tech = match d.k_str("tech") {
                Some(v) => v,
                None => "notech",
            };

            match ret.get_mut(tech) {
                Some(v) => {
                    v.push(d.clone());
                }
                None => {
                    ret.insert(tech.to_string(), vec![d.clone()]);
                }
            }
        }

        if ret.is_empty() {
            throw_err!(ECode::EmptyData, "no data");
        }

        Ok(ret)
    }

    async fn do_load(&self, line: &str, data: &MapSv, client: &Client) -> Resp {
        let tech = data.ck_str("tech")?;
        let tp = data.ck_str("tp")?;

        let body = self.get_pick(line, tp, client).await?;
        let body = body
            .get(tech)
            .ok_or(err_value(ECode::EmptyData, "no data"))?;

        ok!(body)
    }

    async fn do_count(&self, line: &str, client: &Client) -> Resp {
        let mut ret = HashMap::new();

        if let Ok(body) = self.get_pick(line, "inner", client).await {
            for (k, v) in body.iter() {
                let mut tmp = HashMap::new();
                tmp.insert("inner", v.len());

                ret.insert(k.to_string(), tmp);
            }
        }

        if let Ok(body) = self.get_pick(line, "outer", client).await {
            for (k, v) in body.iter() {
                match ret.get_mut(k) {
                    Some(val) => {
                        val.insert("outer", v.len());
                    }
                    None => {
                        let mut tmp = HashMap::new();
                        tmp.insert("outer", v.len());

                        ret.insert(k.to_string(), tmp);
                    }
                }
            }
        }

        if ret.is_empty() {
            throw_err!(ECode::EmptyData, "no data");
        }

        let mut tmp = Vec::new();
        for (k, v) in ret.iter() {
            let inner = match v.get("inner") {
                Some(t) => *t as i64,
                None => 0,
            };

            let outer = match v.get("outer") {
                Some(t) => *t as i64,
                None => 0,
            };

            tmp.push(json!({
                "sn": k,
                "inner": inner,
                "outer": outer,
            }));
        }

        ok!(tmp)
    }

    async fn update_cage(
        &self,
        line: &str,
        psn: &str,
        tsn: &str,
        side_mod: &str,
        client: &Client,
    ) -> RNull {
        let cmd = json!({ "line": line });

        let body = client.cfgdb(&cmd, &format!("{side_mod}/list")).await?;
        let body = body.ck_array("data")?;

        let mut tmp = Vec::new();

        for d in body.iter() {
            let d = d.ckobj()?;

            match d.k_str("mt") {
                Some(v) => {
                    if v != "C" {
                        continue;
                    }
                }
                None => {
                    continue;
                }
            }

            match d.k_str("tech") {
                Some(v) => {
                    if v != tsn {
                        continue;
                    }
                }
                None => {
                    continue;
                }
            }

            let sn = d.ck_str("sn")?;

            tmp.push(sn.to_string());
        }

        if tmp.is_empty() {
            return Ok(());
        }

        tmp.sort_unstable();

        let cage_sn = &tmp[0];

        warn!("update side cage: {:?}", cage_sn);

        if let Err(e) = update_side(line, &side_mod, cage_sn, 1, false, client).await {
            warn!("error update side: {:?}", e);
        }

        let opt = json!({
            "tab": "consume",
            "psn": psn,
            "wsn": "auto",
            "data": {
                "picknum": 1,
            }
        });

        if let Err(e) = update_stock(line, &opt, cage_sn, "cage", 1, client).await {
            warn!("error update stock: {:?}", e);
        }

        Ok(())
    }

    async fn update_ball(
        &self,
        line: &str,
        psn: &str,
        side_mod: &str,
        body: &MapSv,
        client: &Client,
    ) -> RNull {
        let ball_sn = body.ck_str("bsn")?;
        let mt = body.ck_str("mt")?;
        let ball_num = body.ck_i64("num")?;
        let mt_mod = self.get_mt_type(mt)?;

        warn!("update side ball: {:?}", ball_sn);

        if let Err(e) = update_side(line, &side_mod, ball_sn, ball_num, false, client).await {
            warn!("error update side: {:?}", e);
        }

        let opt = json!({
            "tab": "consume",
            "psn": psn,
            "wsn": "auto",
            "data": {
                "picknum": ball_num,
            }
        });

        if let Err(e) = update_stock(line, &opt, ball_sn, &mt_mod, ball_num, client).await {
            warn!("error update stock: {:?}", e);
        }

        Ok(())
    }

    #[instrument(skip_all, name = "do_chunk")]
    async fn do_chunk(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let psn = data.ck_str("sn")?;
        let tsn = data.ck_str("tsn")?;
        let wsn = data.ck_str("wsn")?;

        let cmd = json!({
            "line": line,
            "k": psn,
        });

        let body = client.cfgdb(&cmd, "psn/get").await?;
        let body = body.ck_obj("data")?;

        let insn = body.ck_str("insn")?;

        let mut ret = MapSv::new();
        let field = vec!["standout", "dent", "angle", "kia", "sia", "kea", "sea"];

        for d in field.iter() {
            self.save_data(d, &body, data, &mut ret);
        }

        if ret.is_empty() {
            return ok!();
        }

        if let None = body.k_str_empty_trim("boxpack") {
            let side_mod = self.get_side_type("auto")?;

            let _ = self.update_ball(line, psn, &side_mod, &body, client).await;
            let _ = self.update_cage(line, psn, tsn, &side_mod, client).await;

            let cmd = json!({ "line": line, "k": insn });
            let _ = client.cfgdb(&cmd, &format!("inner/rm")).await?;

            let cmd = json!({ "line": line, "k": psn });
            let _ = client.cfgdb(&cmd, &format!("outer/rm")).await?;

            ret.insert("boxpack".to_string(), json!("auto"));
        }

        let cmd = json!({
            "line": line,
            "tab": "autoproduct",
            "data": [{
                "psn": psn,
                "wsn": wsn,
                "k":  {
                    "tsn": tsn,
                },
                "v": ret
            }]
        });

        let _ = client.timedb(&cmd, "chunk").await;

        warn!("update: {:?}", &ret);

        let cmd = json!({
            "line": line,
            "k": psn,
            "v": ret,
        });

        client.cfgdb(&cmd, "psn/md").await
    }

    fn save_data(&self, key: &str, old: &MapSv, data: &MapSv, ret: &mut MapSv) {
        if let Some(val) = data.k_f64(key) {
            if self.is_eq(val, 0.0) {
                return;
            }

            match old.k_f64(key) {
                Some(old_val) => {
                    if !self.is_eq(old_val, val) {
                        ret.insert(key.to_string(), json!(val));
                    }
                }
                None => {
                    ret.insert(key.to_string(), json!(val));
                }
            }
        }
    }

    async fn do_box(&self, line: &str, data: &MapSv, client: &Client) -> Resp {
        let box_sn = data.ck_str("k")?;

        let cmd = json!({
            "line": line,
        });

        let body = client
            .cfgdb(&cmd, &format!("file/list/box_{box_sn}"))
            .await?;
        let body = body.ck_array("data")?;

        let mut ret = Vec::new();

        for d in body.iter() {
            let psn = d.ck_str("sn")?;
            if psn == "attr" {
                continue;
            }

            let cmd = json!({
                "line": line,
                "k": psn,
            });

            let psn_body = match client.cfgdb(&cmd, "psn/get").await {
                Ok(v) => v,
                Err(_) => {
                    continue;
                }
            };

            let psn_body = psn_body.ck_obj("data")?;
            ret.push(psn_body.clone());
        }

        if ret.is_empty() {
            throw_err!(ECode::EmptyData, "no data");
        }

        ok!(ret)
    }

    async fn do_list(&self, line: &str, client: &Client) -> Resp {
        let cmd = json!({
            "line": line,
        });

        let mut body = client.cfgdb(&cmd, "pick/list").await?;
        let body = body.ck_array_mut("data")?;

        for d in body.iter_mut() {
            let d = d.ckobj_mut()?;
            let _ = self.get_mt(line, d, client).await;
        }

        ok!(body)
    }

    async fn get_mt(&self, line: &str, data: &mut MapSv, client: &Client) -> RNull {
        let bsn = data.ck_str("bsn")?;
        let mt = data.ck_str("mt")?;

        let tp = self.get_mt_type(mt)?;

        let cmd = json!({
            "line": line,
            "k": bsn,
        });

        let body = client.cfgdb(&cmd, &format!("{tp}/get")).await?;
        let body = body.ck_obj("data")?;

        if let Some(ball) = body.k_f64("ball") {
            data.insert("ball".to_string(), json!(ball));
        }

        if let Some(std) = body.k_f64("std") {
            data.insert("std".to_string(), json!(std));
        }

        if let Some(sub) = body.k_f64("sub") {
            data.insert("sub".to_string(), json!(sub));
        }

        if let Some(pos) = body.k_str("pos") {
            data.insert("pos".to_string(), json!(pos));
        }

        Ok(())
    }
}
